function Dictionary(startValues){
	this.values = startValues || {};
}
Dictionary.prototype.store = function(name, value){
	this.values[name] = value;
}
Dictionary.prototype.lookup = function(name){
	return this.values[name];
}
Dictionary.prototype.contains = function(name){
	return Object.prototype.hasOwnProperty.call(this.values, name) &&
		Object.prototype.propertyIsEnumerable.call(this.values, name);
}
Dictionary.prototype.each = function(action)
{
	forEachIn(this.values, action);
}

var creatureTypes = new Dictionary();
creatureTypes.register = function(constructor){
	this.store(constructor.prototype.character, constructor);
};

BouncingBug.prototype.character = "%";
creatureTypes.register(BouncingBug);

function StupidBug() {};
StupidBug.prototype.character = "s";
StupidBug.prototype.act = function(surroundings){
	return {type: "move", direction: "s"};
};	
creatureTypes.register(StupidBug);

function method(object, name){
	return function(){
		object[name].apply(object, arguments);
	};
}

function clone(object)
{
	function OneShotConstructor(){}
	OneShotConstructor.prototype = object;
	return new OneShotConstructor();
}

function LifeLikeTerrarium(plan){
	Terrarium.call(this, plan);
}

LifeLikeTerrarium.prototype = clone(Terrarium.prototype);
LifeLikeTerrarium.prototype.constructor = LifeLikeTerrarium;


//var printHere = inPlacePrinter();
//printHere("Now you see it");

function bind(func, object){
	return function(){
		return func.apply(object, arguments);
	};
}


function Point(x, y){
	this.x = x;
	this.y = y;
}

Point.prototype.add = function(other){
	return new Point(this.x + other.x, this.y + other.y);
}

Point.prototype.isEqualTo = function(other){
	return this.x == other.x && this.y == other.y;
}

Point.prototype.toString = function(){
	return "(" + this.x + "," + this.y + ")";
};

//var annoy = setInterval(function() {document.write("what?");}, 400);


function Grid(width, height){
	this.width = width;
	this.height = height;
	this.cells = new Array(width * height);
}

Grid.prototype.valueAt = function(point)
{
	if(this.isInside(point))
	{
		return this.cells[point.y * this.width + point.x];
	}
	return undefined;	
}

Grid.prototype.setValueAt = function(point,value){
	if(this.isInside(point)){
		this.cells[point.y * this.width + point.x] = value;
	}
}

Grid.prototype.isInside = function(point){
	return point.x >= 0 && point.x < this.width && point.y >= 0 && point.y < this.height;
}

Grid.prototype.moveValue = function(from, to){	
	this.setValueAt(to, this.valueAt(from));
	this.setValueAt(from, undefined);
};

Grid.prototype.each = function(action){
	for(var y = 0; y<this.height; y++){
		for(var x = 0; x<this.width; x++){
			var point = new Point(x, y);
			action(point, this.valueAt(point));
		}
	}
};
/*
var testGrid = new Grid(3, 2);
testGrid.setValueAt(new Point(1, 0), "#");
testGrid.setValueAt(new Point(1, 1), "o");
testGrid.each(function(point, value){
	document.write(point.x + "," + point.y + ":" + value);
	document.write('<br>');
});*/

var directions = new Dictionary(
  {"n":  new Point( 0, -1),
   "ne": new Point( 1, -1),
   "e":  new Point( 1,  0),
   "se": new Point( 1,  1),
   "s":  new Point( 0,  1),
   "sw": new Point(-1,  1),
   "w":  new Point(-1,  0),
   "nw": new Point(-1, -1)});

var point = new Point(4, 4);
point.add(directions.lookup("se"));

var thePlan = [" ##", "#s#"];           

var terrarium = new Terrarium(thePlan);
var string = terrarium.toString();
document.write(string);
            

var wall = {};
wall.character = "#";
StupidBug.prototype.character = "o";

function Terrarium(plan){
	var grid = new Grid(plan[0].length, plan.length);
	for(var y = 0; y<plan.length; y++)
	{
		var line = plan[y];
		for(var x = 0; x<line.length; x++){
			grid.setValueAt(new Point(x, y),
							elementFromCharacter(line.charAt(x)));
		}
	}
	this.grid = grid;
};

Terrarium.prototype.listActingCreatures = function(){
	var found = [];
	this.grid.each(function(point, value){
		if(value != undefined && value.act){
			found.push({object:value, point:point});
		}
	});
	return found;
};

Terrarium.prototype.listSurroundings = function(center){
	var result = [];
	var grid = this.grid;
	directions.each(function(name, direction){
		var place = center.add(direction);
		if(grid.isInside(place)){
			result[name] = characterFromElement(grid.valueAt(place));
		}
		else{
			result[name] = "#";
		}
	});
	return result;	
};

Terrarium.prototype.start = function(){
	if(!this.running){
		this.running = setInterval(bind(this.step, this), 500);
	}
};

Terrarium.prototype.stop = function(){
	if(this.running){
		clearInterval(this.running);
		this.running = null;
	}
}




Terrarium.prototype.processCreature = function(creature){
	var surroundings = this.listSurroundings(creatture.point);
	var action = creature.object.act(surroundings);
	if(action.type == "move" && directions.contains(action.direction)){
		var to = creature.point.add(directions.loopup(action.direction));
		if(this.grid.isInside(to) && this.grid.valueAt(to) == undefined){
			this.grid.moveValue(creature.point, to);
		}
	}
	else{
		throw new Error("Unsupported action: " + action.type);
	}
};

Terrarium.prototype.step = function(){
	forEach(this.listActingCreatures(), 
		bind(this.processCreature, this));
		
	if(this.onStep){
		this.onStep();
	}	
};
	

Terrarium.prototype.toString = function(){
	var characters = [];
	var endOfLine = this.grid.width - 1;
	this.grid.each(function(point, value){
		characters.push(characterFromElement(value));
		if(point.x == endOfLine){
			character.push("\n");
		}
	});
	return characters.join("");
};

LifeLikeTerrarium.prototype.processCreature = function(creature) {
  var surroundings = this.listSurroundings(creature.point);
  var action = creature.object.act(surroundings);

  var target = undefined;
  var valueAtTarget = undefined;
  if (action.direction && directions.contains(action.direction)) {
    var direction = directions.lookup(action.direction);
    var maybe = creature.point.add(direction);
    if (this.grid.isInside(maybe)) {
      target = maybe;
      valueAtTarget = this.grid.valueAt(target);
    }
  }

  if (action.type == "move") {
    if (target && !valueAtTarget) {
      this.grid.moveValue(creature.point, target);
      creature.point = target;
      creature.object.energy -= 1;
    }
  }
  else if (action.type == "eat") {
    if (valueAtTarget && valueAtTarget.energy) {
      this.grid.setValueAt(target, undefined);
      creature.object.energy += valueAtTarget.energy;
    }
  }
  else if (action.type == "photosynthese") {
    creature.object.energy += 1;
  }
  else if (action.type == "reproduce") {
    if (target && !valueAtTarget) {
      var species = characterFromElement(creature.object);
      var baby = elementFromCharacter(species);
      creature.object.energy -= baby.energy * 2;
      if (creature.object.energy > 0)
        this.grid.setValueAt(target, baby);
    }
  }
  else if (action.type == "wait") {
    creature.object.energy -= 0.2;
  }
  else {
    throw new Error("Unsupported action: " + action.type);
  }

  if (creature.object.energy <= 0)
    this.grid.setValueAt(creature.point, undefined);
};


function elementFromCharacter(character){	
	if(character == " "){
		return undefined;
	}
	else if(character == "#"){
		return wall;
	}
	else if(creatureTypes.contains(character)){
		return new (creatureTypes.lookup(character))();
	}
	else
	{
		throw new Error("Unknown character: " + character);
	}
};

//new types of bug
function BouncingBug(){
	this.direction = "ne";
}
BouncingBug.prototype.act = function(surroundings){
	if(surroundings[this.direction] != " "){
		this.direction = (this.direction == "ne" ? "sw":"ne");
	}
	return {type:"move", direction:this.direction};
}




function characterFromElement(element){
	if(element == undefined){
		return " ";
	}
	else {
		return element.character;
	}
};



	




/*

var colours = new Dictionary({Grover: "blue",
							  Elmo: "orange",
							  Bert: "yellow"});
document.write(colours.contains("Grover"));
document.write(colours.contains("constructor"));

colours.each(function(name, colour){
	document.write(name + " is " + colour);
	document.write('<br>');
});*/
							  
//var object = {foo:"bar"};
//document.write(Object.prototype.hasOwnProperty.call(object, "foo"));
//document.write(Object.prototype.propertyIsEnumerable.call(object, "foo"));

Object.prototype.properties = function()
{
	var result = [];
	for (var property in this)	{
		if(this.hasOwnProperty(property))		{
			result.push(property);
		}
	}
	return result;
}
function forEachIn(object, action)
{
	for(var property in object){
		if(object.hasOwnProperty(property)){
			action(property, object[property]);
		}
	}
}

document.write("No grammer error!");

/*
var chimera = {head:"lion", body:"goat", tail:"snake"};
forEachIn(chimera, function(name, value){
	document.write("The " + name + "of a " + value + ".");
	document.write('<br>');
})	
	
var test = {x:10, y:3};*/

		
/*
function Rabbit(adjective)
{
	this.adjective = adjective;
}

Rabbit.prototype.speak = function(line){
	document.write("The " + this.adjective + " rabbit says ' " + line + "'");
	document.write('<br>');
}

var hazelRabbit = new Rabbit("hazel");
hazelRabbit.speak("Good Frith");
*/


	
//alert(simpleObject.constructor);
//alert(simpleObject.toString);
/*
function Rabbit(adjective)
{
	this.adjective = adjective;
	this.speak = function(line){
		document.write("The " + this.adjective + " rabbit says ' " + line + "'");
		document.write('<br>');
	};
}

var killerRabbit = new Rabbit("killer");
killerRabbit.speak("Gaaaa");

killerRabbit.teeth = "long, sharp, and bloody";
document.write(killerRabbit.teeth);
*/


//alert(killerRabbit.toString == simpleObject.toString);

//alert(killerRabbit.constructor);



//rabbit.speak("I am a rabbit");

